![]() Acrobat file (191K) |
![]() ClarisWorks 4 file (44K) |
![]() QuickView file (284K) |
Technote 1026 | FEBRUARY 1996 |
If you're an application or app framework developer and want to ensure the windows in your application(s) are always updated and activated properly, you should read this Note.
This Technote augments the information presented in three chapters of Inside Macintosh
:"Event Manager" (Chapter 2) and "Window Manager" (Chapter 4) of Macintosh Toolbox Essentials
and "Notification Manager" (Chapter 5) of Processes
Contents
The fact that Dialog Manager, and in particular ModalDialog and the standard dialog filter, provide imperfect event handling means that some window-oriented events are "swallowed" (i.e., never provided to your app) while the dialog is present.
When the user dismisses NM's dialog, your app receives a redundant activate event for the (recently-reinstated) front window. Your app can make sure it doesn't logically activate a window (i.e., enable text fields, etc.) redundantly by simply checking to see if the window is already active before logically activating it.
Each window has a region, expressed in global coordinates, which describes the portion of the window that needs redrawing. This is called the window's update region. During WaitNextEvent, the Event Manager, Window Manager, and Process Manager collaborate in walking the current window list. If the update region of a window is found to be non-empty, they generate an update event for that window.
When your application receives the update event, it calls BeginUpdate, (re)draws the image for the window, and calls EndUpdate. The BeginUpdate/EndUpdate pair of calls empties the update region for the window so that subsequent searches for windows which need updating do not find that window.
These update regions are the key to understanding how the Notification Manager swallows update events.
The standard dialog filter, to which NM's dialog filter passes control, wants to ensure that background apps get processing time by "solving" the problem described in Macintosh Technical Note TB37, "Pending Update Perils." The standard filter simply calls BeginUpdate and EndUpdate every time it's given an update event, regardless of the window for which the event is bound.
This results in all windows in the current window list having their update regions emptied almost immediately. As a consequence, no update events are generated for those windows, even though they need to be (re)drawn. When the user dismisses NM's dialog, only the windows covered by NM's dialog get update events, and then only for the region covered by NM's dialog.
There is nothing straightforward your app can do to prevent swallowed update events. While your app innocently waits for a call to WaitNextEvent to return, NM suddenly puts up its dialog and seizes control of the event loop until the user dismisses the dialog. Your app can't even predict when this will happen, much less prevent it or easily work around it.
Most users of UpdateRegionSaver will only need to make three very simple calls, shown in this sample: SaveUpdateRegions, RestoreUpdateRegions, and DeleteUpdateRegions.
#ifndef __EVENTS__ # includeFor a full listing of each of the three calls, refer to Appendix A at the end of this Technote.#endif #include "UpdateRegionSaver.h" pascal Boolean WaitNextEventWithNMSafeUpdates (EventMask eventMask, EventRecord *theEvent, UInt32 sleep, RgnHandle mouseRgn) { UpdateRegionSaver *root = SaveUpdateRegions ( ); EventRecord event; Boolean result = WaitNextEvent (eventMask,theEvent,sleep,mouseRgn); RestoreUpdateRegions (root); DeleteSavedUpdateRegions (root); root = nil; return result; }
typedef struct UpdateRegionSaver { RgnHandle fRgnH; WindowRef fRef; struct UpdateRegionSaver *fNext; } UpdateRegionSaver;
SaveUpdateRegions returns a pointer to the root of simple singly-linked list which contains a node for each window. In each node your app will find a window pointer, a copy of the window's update region, and a pointer to the next node.
RestoreUpdateRegions copies the regions in the list and converts the copies to the local coordinates of each window before calling InvalRgn to merge the region into any update region which may already be there.
#pragma once #ifndef __WINDOWS__ # include <Windows.h>#endif typedef struct UpdateRegionSaver { // We don't care about alignment since this is an internal // runtime-only structure. RgnHandle fRgnH; WindowRef fRef; struct UpdateRegionSaver *fNext; } UpdateRegionSaver; // I can't figure why this next #ifdef would be necessary for // pascal funcs,but CW7 for PPC says it is. #ifdef __cplusplus extern "C" { #endif pascal void RestoreUpdateRegions (UpdateRegionSaver *); pascal void DeleteSavedUpdateRegions (UpdateRegionSaver *); pascal UpdateRegionSaver * SaveUpdateRegions (void); #ifdef __cplusplus } #endif
#ifndef __LOWMEM__ # include <LowMem.h>#endif #include "UpdateRegionSaver.h" static pascal Boolean IsWindowStillAround (WindowRef ref) { WindowRef scan = LMGetWindowList ( ); while (scan) { if (scan == ref) break; scan = GetNextWindow (scan); } return !!scan; } pascal void RestoreUpdateRegions (UpdateRegionSaver *ursp) { while (ursp) { if (!EmptyRgn (ursp->fRgnH) && IsWindowStillAround (ursp->fRef)) { RgnHandle localUpdateRgn = NewRgn ( ); if (localUpdateRgn) { Point zero; GrafPtr keep = qd.thePort; SetPort (ursp->fRef); zero.h = qd.thePort->portRect.left; zero.v = qd.thePort->portRect.top; GlobalToLocal (&zero); CopyRgn (ursp->fRgnH,localUpdateRgn); OffsetRgn (localUpdateRgn,zero.h,zero.v); InvalRgn (localUpdateRgn); SetPort (keep); DisposeRgn (localUpdateRgn); } } ursp = ursp->fNext; } } pascal void DeleteSavedUpdateRegions (UpdateRegionSaver *ursp) { while (ursp) { UpdateRegionSaver *next = ursp->fNext; DisposeRgn (ursp->fRgnH); DisposePtr ((Ptr) ursp); ursp = next; } } pascal UpdateRegionSaver * SaveUpdateRegions (void) { // // This function saves as many update regions as it can. // If for some reason memory is so low that some regions // cannot be saved, this function makes a best effort. // (Its best effort is rather stupid, but it does try.) // UpdateRegionSaver *root = nil; WindowRef scan = LMGetWindowList ( ); while (scan) { UpdateRegionSaver *newUpdateRegionSaver = (UpdateRegionSaver *) NewPtr (sizeof (UpdateRegionSaver)); if (!MemError ( )) { RgnHandle rgnH = NewRgn ( ); if (!rgnH) { DisposePtr ((Ptr) newUpdateRegionSaver); newUpdateRegionSaver = nil; } else { GetWindowUpdateRgn (scan,rgnH); newUpdateRegionSaver->fRgnH = rgnH; newUpdateRegionSaver->fRef = scan; newUpdateRegionSaver->fNext = root; root = newUpdateRegionSaver; } } scan = GetNextWindow (scan); } return root; }